package com.tt5.services.handlers.user

import com.tt5.services.bean.UserInfo
import com.tt5.services.error.ApiException
import com.tt5.services.handlers.AppRouter
import com.tt5.services.handlers.user.AuthHandler.fetchUserInfo
import com.tt5.services.vertx.*
import com.tt5.services.vertx.handlers.failure.FailureHandler
import io.netty.handler.codec.http.HttpResponseStatus
import io.vertx.core.*
import io.vertx.core.http.HttpHeaders
import io.vertx.core.json.Json
import io.vertx.core.json.JsonObject
import io.vertx.ext.auth.User
import io.vertx.ext.auth.authentication.AuthenticationProvider
import io.vertx.ext.auth.authentication.Credentials
import io.vertx.ext.auth.authentication.UsernamePasswordCredentials
import io.vertx.ext.web.Router
import io.vertx.ext.web.RoutingContext
import io.vertx.ext.web.handler.BasicAuthHandler
import io.vertx.ext.web.handler.JWTAuthHandler
import io.vertx.ext.web.handler.impl.HTTPAuthorizationHandler
import io.vertx.kotlin.core.json.jsonObjectOf
import io.vertx.kotlin.coroutines.coAwait
import io.vertx.sqlclient.Pool
import io.vertx.sqlclient.Tuple

object AuthHandler {
  fun route(vertx: Vertx, router: Router) {
    router.get("/login").handler(
      BasicAuthHandler.create(AppAuthenticationProvider(vertx, vertx.sharedDbPool()))
    ).coroutineHandler(::generateJwtToken).failureHandler(
      FailureHandler(::processLoginFail)
    )
    router.route().handler(
      JWTAuthHandler.create(AppRouter.jwtAuth)
    ).failureHandler(
      FailureHandler(::processLoginFail)
    )
    router.get("/jwt-refresh").coroutineHandler(::refreshJwtToken)
  }

  /**
   * 生成jwt鉴权信息
   */
  suspend fun generateJwtToken(ctx: RoutingContext) {
    val redis = ctx.redis.connect().coAwait()
    val userInfo: UserInfo = ctx.user().get("detailInfo")
    var oldSessionId: String? = redis.get("${userInfo.userid}").coAwait()?.toString()
    if (oldSessionId != null && oldSessionId.isNotEmpty()) redis.delete(oldSessionId).coAwait()
    oldSessionId = ctx.session().id()
    if (oldSessionId != null && oldSessionId.isNotEmpty()) redis.delete(oldSessionId).coAwait()
    ctx.session().regenerateId()
    redis.set("${userInfo.userid}", ctx.session().id()).coAwait()
    redis.set(ctx.session().id(), Json.encode(userInfo)).coAwait()
    val refreshToken = AppRouter.jwtAuth.generateToken(
      jsonObjectOf(), AppRouter.jwtRefreshOpt
    )
    val accessToken = AppRouter.jwtAuth.generateToken(
      jsonObjectOf(), AppRouter.jwtAccessOpt
    )
    ctx.json(
      jsonObjectOf(
        "refreshToken" to refreshToken,
        "refreshExpiresInMinutes" to AppRouter.jwtRefreshOpt.expiresInSeconds / 60,
        "accessToken" to accessToken,
        "accessExpiresInMinutes" to AppRouter.jwtAccessOpt.expiresInSeconds / 60,
      )
    )
  }

  /**
   * 刷新jwt访问信息
   */
  suspend fun refreshJwtToken(ctx: RoutingContext) {
    val accessToken = AppRouter.jwtAuth.generateToken(
      jsonObjectOf(), AppRouter.jwtAccessOpt
    )
    ctx.json(
      jsonObjectOf(
        "accessToken" to accessToken,
        "accessExpiresInMinutes" to AppRouter.jwtAccessOpt.expiresInSeconds / 60,
      )
    )
  }

  fun processLoginFail(ctx: RoutingContext): Boolean {
    if (ctx.statusCode() == HttpResponseStatus.BAD_REQUEST.code()) throw ApiException(
      code = HttpResponseStatus.BAD_REQUEST.code(),
      content = "登录信息错误",
      errorMessage = "${HttpHeaders.AUTHORIZATION} 格式错误或内容解析失败"
    )
    if (ctx.statusCode() == HttpResponseStatus.UNAUTHORIZED.code()) throw ApiException(
      code = HttpResponseStatus.UNAUTHORIZED.code(),
      content = "登录信息错误",
      errorMessage = "未提供${HttpHeaders.AUTHORIZATION}或类型不是${HTTPAuthorizationHandler.Type.BASIC}"
    )
    return false
  }

  suspend fun fetchUserInfo(dbPool: Pool, username: String, password: String): User {
    val rs = dbPool.withConnection { connection ->
      connection.preparedQuery(
        "select * from users where name=? and password=sha2(?, 256)"
      ).execute(Tuple.of(username, password))
    }.coAwait()
    if (rs.size() < 1) throw ApiException(
      code = 400, error = "404", content = "用户${username}未找到或密码错误"
    )
    if (rs.size() > 1) throw ApiException(
      code = 500, content = "服务器内部错误", errorMessage = "用户${username}数据存在多条：${rs.size()}"
    )
    val userInfo = rs.first().toJson().mapTo(UserInfo::class.java)
    val user = User.fromName(userInfo.name)
    user.attributes().put("detailInfo", userInfo)
    return user
  }
}

class AppAuthenticationProvider(val vertx: Vertx, val dbPool: Pool) : AuthenticationProvider {
  @Deprecated("Deprecated in Java")
  override fun authenticate(
    credentials: JsonObject, resultHandler: Handler<AsyncResult<User>>
  ) {
    authenticate(UsernamePasswordCredentials(credentials)).onComplete(resultHandler)
  }

  override fun authenticate(credentials: Credentials): Future<User> {
    if (credentials !is UsernamePasswordCredentials) throw ApiException(
      code = HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
      content = "服务器内部错误",
      errorMessage = "期望credentials类型为UsernamePasswordCredentials，实际为：${credentials.javaClass.name}"
    )
    if (credentials.username == null || credentials.username.length < 6) throw ApiException(
      code = HttpResponseStatus.BAD_REQUEST.code(),
      content = "参数username错误",
    )
    if (credentials.password == null || credentials.password.length < 6) throw ApiException(
      code = HttpResponseStatus.BAD_REQUEST.code(),
      content = "参数password错误",
    )
    val promise = Promise.promise<User>()
    vertx.launch {
      try {
        promise.complete(
          fetchUserInfo(dbPool, credentials.username, credentials.password)
        )
      } catch (e: Throwable) {
        promise.fail(e)
      }
    }
    return promise.future()
  }
}
